%{
    #include "node.h"
    #include <stdio.h>
    #include <iostream>
    NBlock *programBlock; /* the top level root node of our final AST */

    extern int yylex();
    void yyerror(const char *s) { printf("ERROR: %s\n", s); }
%}

%debug

%union {
    Node *node;
    NBlock *block;
    NExpression *expr;
    NStatement *stmt;
    NIdentifier *ident;
    NDeclarationType *decl_type;
    NVariableDeclaration *var_decl;
    std::vector<NVariableDeclaration*> *varvec;
    std::vector<NExpression*> *exprvec;
    std::string *string;
    int token;
}

%token <string> TIDENTIFIER TINTEGER TDOUBLE
%token <string> TINT TFLOAT TVOID 
%token <string> TIF TELSE TELSEIF TFOR TWHILE
%token <string> TTRUE TFALSE
%token <string> TPRINT TRETURN

%token <token> TCEQ TCNE TCLT TCLE TCGT TCGE TEQUAL
%token <token> TLPAREN TRPAREN TLBRACE TRBRACE TCOMMA TDOT TQUOTA
%token <token> TPPLUS TMMINUS TAND TOR
%token <token> TPLUS TMINUS TMUL TDIV
%token <token> TTERMINATE TEXTERN


%type <ident> ident
%type <decl_type> decl_type
%type <expr> numeric expr 
%type <varvec> func_decl_args
%type <exprvec> call_args
%type <block> program stmts block
%type <stmt> stmt var_decl func_decl extern_decl
%type <token> comparison comparisons

%left TPLUS TMINUS
%left TMUL TDIV
%left TAND TOR

%right TMMINUS TPPLUS

%start program

%%

program : stmts { programBlock = $1; }
        ;
        
stmts : stmt { $$ = new NBlock(); $$->statements.push_back($<stmt>1); }    
      | stmts stmt { $1->statements.push_back($<stmt>2); }
      ;

stmt : func_decl
     | var_decl
     | extern_decl
     | TIF TLPAREN expr TRPAREN block { $$ = new NIfStatement($3, $5);}
     | TIF TLPAREN expr TRPAREN block TELSE block { $$ = new NIfStatement($3, $5, $7);}
     | TFOR TLPAREN var_decl expr expr TRPAREN block { $$ = new NForStatement($3, $4, $5, $7); }
     | TWHILE TLPAREN expr TRPAREN block { $$ = new NWhileStatement($3, $5); }
     | expr { $$ = new NExpressionStatement(*$1); }
     | TRETURN expr { $$ = new NReturnStatement(*$2); }
     ;

block : TLBRACE stmts TRBRACE { $$ = $2; }
      | TLBRACE TRBRACE { $$ = new NBlock(); }
      ;

var_decl : decl_type ident { $$ = new NVariableDeclaration(*$1, *$2); }
         | decl_type ident TEQUAL expr{ $$ = new NVariableDeclaration(*$1, *$2, $4); }
         ;

extern_decl : TEXTERN decl_type ident TLPAREN func_decl_args TRPAREN
                { $$ = new NExternDeclaration(*$2, *$3, *$5); delete $5; }
            ;

func_decl : decl_type ident TLPAREN func_decl_args TRPAREN block 
            { $$ = new NFunctionDeclaration(*$1, *$2, *$4, *$6); delete $4; }
          ;

func_decl_args : /*blank*/  { $$ = new VariableList(); }
          | var_decl { $$ = new VariableList(); $$->push_back($<var_decl>1); }
          | func_decl_args TCOMMA var_decl { $1->push_back($<var_decl>3); }
          ;

ident : TIDENTIFIER { $$ = new NIdentifier(*$1); delete $1; }
      ;

decl_type : TINT { $$ = new NDeclarationType(*$1); delete $1; }
          | TFLOAT { $$ = new NDeclarationType(*$1); delete $1; }
          | TVOID { $$ = new NDeclarationType(*$1); delete $1; }
          ;

numeric : TINTEGER { $$ = new NInteger(atol($1->c_str())); delete $1; }
        | TDOUBLE { $$ = new NDouble(atof($1->c_str())); delete $1; }
        | TTRUE { $$ = new NBool(*$1); delete $1; }
        | TFALSE { $$ = new NBool(*$1); delete $1; }
        ;
    
expr : ident TEQUAL expr { $$ = new NAssignment(*$<ident>1, *$3); }
     | ident TLPAREN call_args TRPAREN { $$ = new NMethodCall(*$1, *$3); delete $3; }
     | ident { $<ident>$ = $1; }
     | numeric     
     | expr comparisons { $$ = new NBinaryOperator(*$1, $2, *(new NExpression())); }
     | expr TMUL expr { $$ = new NBinaryOperator(*$1, $2, *$3); }
     | expr TDIV expr { $$ = new NBinaryOperator(*$1, $2, *$3); }
     | expr TPLUS expr { $$ = new NBinaryOperator(*$1, $2, *$3); }
     | expr TMINUS expr { $$ = new NBinaryOperator(*$1, $2, *$3); }
     | expr comparison expr { $$ = new NBinaryOperator(*$1, $2, *$3); }
     | TLPAREN expr TRPAREN { $$ = $2; }
     | TPRINT TLPAREN TQUOTA ident TQUOTA TRPAREN { }
     ;

call_args : /*blank*/  { $$ = new ExpressionList(); }
          | expr { $$ = new ExpressionList(); $$->push_back($1); }
          | call_args TCOMMA expr  { $1->push_back($3); }
          ;

comparison : TCEQ | TCNE | TCLT | TCLE | TCGT | TCGE 
           | TAND | TOR
           ;

comparisons : TPPLUS
            | TMMINUS
            ;
%%